<!--
Copyright 2011 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<html>
<head>
  <title>Local Storage Scope</title>
  <link rel="stylesheet" type="text/css" href="scope.css">
  <meta charset="utf-8">
</head>
<body>
  <h1>Local Storage Scope</h1>
  <div id="live">
  </div>
  <div id="log">
  </div>
  <script>

"use strict";

var log = document.getElementById('log');
var liveDiv = document.getElementById('live');

function addEntry(cls, text) {
  var e = document.createElement('p');
  e.className = cls;
  e.appendChild(document.createTextNode(text));
  log.appendChild(e);
}

function span(cls, text) {
  var e = document.createElement('span');
  e.className = cls;
  e.appendChild(document.createTextNode(text));
  return e;
}


var colors = [];
function nextColor() {
  if (colors.length == 0) {
    colors = ['c01', 'c02', 'c03', 'c04', 'c05', 'c06'];
  }
  return colors.shift();
}

var tStart = null;
var tLast = null;
var inFlight = {};
var live = {};

function startBar(key) {
  if (key in inFlight) {
    return inFlight[key].color;
  }
  
  var color = nextColor();
  var firstColor = color + ' cFirst';
  var bar = document.createElement('div');
  bar.className = 'bar ' + firstColor;
  log.appendChild(bar);
  
  inFlight[key] = {
    bar: bar,
    color: color
  };
  
  return firstColor
}

function extendBars() {
  for (var key in inFlight) {
    if ('bar' in inFlight[key]) {
      var line = document.createElement('p');
      line.appendChild(document.createTextNode(' '));
      inFlight[key].bar.appendChild(line);
    }
  }
}

function endBar(key) {
  delete inFlight[key];
}

function endAllBars() {
  inFlight = { };
}

var liveList;
function rebuildLive() {
  var now = Date.now();
  var newList = document.createElement('ul');
  
  var items = [];
  for (var k in live) {
    items.push({ t: live[k] - now, id: k });
  }
  items.sort(function(a,b) {
    var c = a.t - b.t;
    if (c != 0) return c;
    if (a.id < b.id) return -1;
    if (a.id > b.id) return 1;
    return 0;
  });
  for (var i in items) {
    var li = document.createElement('li');
    if (items[i].t < -1000) { li.className = 'old'; }
    li.appendChild(span('timestamp', items[i].t));
    li.appendChild(document.createTextNode(ppUUID(items[i].id)));
    newList.appendChild(li);
  }
  if (liveList) {
    liveDiv.replaceChild(newList, liveList);
  } else {
    liveDiv.appendChild(newList);
  }
  liveList = newList;
}
setInterval(rebuildLive, 2000);

function ppUUID(uuid) {
  return uuid.substring(0,6) + '…'
}

function safeParse(value) {
  try {
    return JSON.parse(value);
  } catch (e) {
    return value;
  }
}

function safeGetProperty(value, key, def) {
  if (typeof value == 'object' && value.hasOwnProperty(key)) {
    return value[key];
  }
  return def;
}

function pp(value) {
  return JSON.stringify(value, null, 2);
}

function lsmEntry(op, id, tx, val) {
  var msg;
  var t;
  var ts = " ";
  if (val) {
    try {
      var payload = JSON.parse(val);
      t = payload.t;
      msg = payload.m;

      if (tStart === null) { tStart = tLast = t; }
      var tRel = t - tStart;
      var tDelta = t - tLast;
      tLast = t;
      ts = "+" + tDelta;
      
    } catch (e) { }
  }

  if (op == 'live') {
    if (val) {
      live[tx] = t;
    } else {
      delete live[tx];
    }
    return;
  };
  
  var cls = val ? 'ls-set' : 'ls-remove';
  cls += ' msg-' + op;
  
  var trackable = op == 'invoke' || op == 'response';
  if (trackable) {
    var clr = startBar(tx);
    cls += ' ' + clr;
  }
  extendBars();   
  if (trackable && op == 'response' && val == null) {
    endBar(tx);
  }

  var p = document.createElement('p');
  p.className = cls;

  p.appendChild(span('timestamp', ts));
  
  if (val) {
    try {
      var data = safeParse(safeGetProperty(msg, 'data', undefined));
      var value = safeGetProperty(data, 'value', data);

      if (op == 'invoke') {
        val = (msg.method + '\n\n'
                + pp(value) + '\n\n'
                + 'bcap: ' + msg.ser + '\n\n'
                + 'reply: ' + msg.reply);
      } else if (op == 'response') {
        if (msg.type == 'success') {
          op += " ✓";
          val = value === undefined ? '-- no data --' : pp(value);
        } else if (msg.type == 'failure') {
          op += " ✗";
          val = pp(data);
        } else {
          throw "huh?"
          val = pp(msg);
        }
      } else if (op == 'start') {
        msg = safeParse(msg);
        value = safeGetProperty(msg, 'value', msg);
        val = pp(value);
      } else {
        val = pp(msg);
      }
    } catch(e) { console.log(e); }
    p.title = val;
  }

  p.appendChild(span('operation', op));
  var dest = 'to ' + ppUUID(id);
  if (tx != '0') {
    dest += ' / ' + ppUUID(tx);
  }
  p.appendChild(span('destination', dest));

  p.appendChild(document.createTextNode(val));
  
  log.appendChild(p);

}
window.addEventListener('storage', function(e) {
  if (e.key === null) {
    addEntry('ls-clear', '-- all local storage cleared --');
    endAllBars();
  } else {
    var parts = e.key.split(',');
    if (parts.length >= 4 && parts[0] == 'lsm') {
      var op = parts[1];
      var id = parts[2];
      var tx = parts[3];
      var val = e.newValue;
      lsmEntry(op, id, tx, val);
    } else {
      extendBars();
      if (e.newValue === null) {
        addEntry('ls-remove', e.key);
      } else {
        addEntry('ls-set', e.key + ' = ' + e.newValue);
      }
    }
  }
}, false);

  </script>
</body>
</html>
